Complexity Analysis
1. What is Big-O notation and why is it important?
Big-O notation হলো একটি mathematical notation যা কোনো algorithm এর time complexity বা space complexity কে input size (সাধারণত n দিয়ে denote করা হয়) এর function হিসেবে প্রকাশ করে। এটা মূলত algorithm এর growth rate measure করে — অর্থাৎ input size বাড়লে execution time বা memory usage কতটা বাড়বে সেটা বোঝায়।
গুরুত্বপূর্ণ কারণ:
- এটা আমাদের hardware-independent এবং implementation-independent ভাবে algorithm এর efficiency compare করতে সাহায্য করে
- Large input size এর ক্ষেত্রে algorithm কেমন behave করবে সেটা predict করা যায় (worst-case scenario বোঝা যায়)
- একই সমস্যার different solution এর মধ্য ে কোনটা better সেটা decide করতে সাহায্য করে
- Interview এবং real-world system design এ scalability বোঝার জন্য essential
Growth rate example:
PreviewClick to view details
n = 1,000,000 হলে roughly:
O(1) -> 1 operation
O(log n) -> around 20 operations
O(n) -> 1,000,000 operations
O(n log n) -> around 20,000,000 operations
O(n^2) -> 1,000,000,000,000 operations
এই কারণেই বড় input এ complexity difference huge হয়ে যায়।
Common Big-O order:
PreviewClick to view details
fastest
O(1)
O(log n)
O(n)
O(n log n)
O(n^2)
O(2^n)
O(n!)
slowest
What is the difference between Big-O, Big-Theta (Θ), and Big-Omega (Ω)?
এই তিনটি notation আসলে algorithm এর complexity এর different bounds বোঝায়:
-
Big-O (O) — এটা upper bound নির্দেশ করে। মানে algorithm সর্বোচ্চ কতটা সময় নিতে পারে (worst-case)। উদাহরণ:
O(n²)মানে algorithm এর growth rate n² এর বেশি হবে না। -
Big-Omega (Ω) — এটা lower bound নির্দেশ করে। মানে algorithm সর্বনিম্ন কতটা সময় নেবে (best-case)। উদাহরণ:
Ω(n)মানে algorithm কমপক্ষে n সময় নেবেই। -
Big-Theta (Θ) — এটা tight bound, অর্থাৎ upper এবং lower bound দুটোই একসাথে। যখন কোনো algorithm এর best-case এবং worst-case complexity একই হয়, তখন Θ notation ব্যবহার করা হয়। উদাহরণ:
Θ(n log n)মানে algorithm এর complexity ঠিক n log n এর কাছাকাছি, কম-বেশি হবে না।
সহজভাবে বললে: O = worst case, Ω = best case, Θ = average/exact bound (both upper and lower)।
Precision note:
O,Ω, এবংΘযথাক্রমে asymptotic upper, lower, এবং tight bound; এগুলো নিজেরা worst, best, বা average case বোঝায় না। Best/average/worst হলো কোন input-case analyze করা হচ্ছে, আর notation হলো সেই case-এর growth bound। যেমন linear search-এর worst-case runtimeΘ(n), কিন্তু সেটি একই সঙ্গেO(n²)-ও—যদিওO(n²)tight নয়।
Practical world এ আমরা প্রায়ই "Big-O" বলি কিন্তু আসলে "Big-Theta" বোঝাই, কারণ industry তে সাধারণত tight bound নিয়েই আলোচনা হয়।
Bound diagram:
PreviewClick to view details
runtime
^
|
| upper bound: Big-O
| /
| / actual runtime
| / /
| / /
|/______/________________> n
lower bound: Big-Omega
যদি upper এবং lower দুটো একই growth rate এ tight হয়,
তখন সেটাকে Big-Theta বলা হয়।
How do you calculate the time complexity of nested loops?
Nested loop এর ক্ষেত্রে সাধারণ rule হলো প্রতিটি loop এর complexity multiply করা।
for (int i = 0; i < n; i++) { // O(n)
for (int j = 0; j < n; j++) { // O(n)
cout << i << " " << j << endl; // O(1)
}
}
এখানে outer loop n বার চলে এবং প্রতিটি iteration এ inner loop আবার n বার চলে, তাই total complexity = O(n) × O(n) = O(n²)।
Iteration grid:
PreviewClick to view details
n = 4
j=0 j=1 j=2 j=3
i=0 x x x x
i=1 x x x x
i=2 x x x x
i=3 x x x x
Total = 4 * 4 = 16 = n^2
কিছু variation:
-
Independent nested loops (উপরের example এর মতো) →
O(n × m)যদি দুটো loop এর range আলাদা হয় (n এবং m)। -
Dependent nested loops (inner loop এর range outer loop এর উপর depend করে):
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
cout << i << " " << j << endl;
}
}